{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Similarity Queries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Don't forget to set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import logging\n",
    "logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Folder \"C:\\Users\\chaor\\AppData\\Local\\Temp\" will be used to save temporary dictionary and corpus.\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import tempfile\n",
    "TEMP_FOLDER = tempfile.gettempdir()\n",
    "print('Folder \"{}\" will be used to save temporary dictionary and corpus.'.format(TEMP_FOLDER))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Similarity Interface"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the previous tutorials on [Corpora and Vector Space](Corpora_and_Vector_Spaces.ipynb) and [Topics and Transformations](Topics_and_Transformations.ipynb), we covered what it means to create a corpus in the Vector Space Model and how to transform it between different vector spaces. A common reason for such a charade is that we want to determine **similarity between pairs of documents**, or the **similarity between a specific document** and a set of other documents (such as a user query vs. indexed documents).\n",
    "\n",
    "To show how this can be done in gensim, let us consider the same corpus as in the previous examples (which really originally comes from Deerwester et al.’s [“Indexing by Latent Semantic Analysis”](http://www.cs.bham.ac.uk/~pxt/IDA/lsa_ind.pdf) seminal 1990 article):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2017-05-22 14:27:18,911 : INFO : loading Dictionary object from C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.dict\n",
      "2017-05-22 14:27:18,911 : INFO : loaded C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.dict\n",
      "2017-05-22 14:27:18,921 : INFO : loaded corpus index from C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.mm.index\n",
      "2017-05-22 14:27:18,924 : INFO : initializing corpus reader from C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.mm\n",
      "2017-05-22 14:27:18,929 : INFO : accepted corpus with 9 documents, 12 features, 28 non-zero entries\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MmCorpus(9 documents, 12 features, 28 non-zero entries)\n"
     ]
    }
   ],
   "source": [
    "from gensim import corpora, models, similarities\n",
    "\n",
    "dictionary = corpora.Dictionary.load(os.path.join(TEMP_FOLDER, 'deerwester.dict'))\n",
    "corpus = corpora.MmCorpus(os.path.join(TEMP_FOLDER, 'deerwester.mm')) # comes from the first tutorial, \"Corpora and Vector Space\"\n",
    "print(corpus)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To follow Deerwester’s example, we first use this tiny corpus to define a 2-dimensional LSI space:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2017-05-22 14:27:19,048 : INFO : using serial LSI version on this node\n",
      "2017-05-22 14:27:19,050 : INFO : updating model with new documents\n",
      "2017-05-22 14:27:19,054 : INFO : preparing a new chunk of documents\n",
      "2017-05-22 14:27:19,057 : INFO : using 100 extra samples and 2 power iterations\n",
      "2017-05-22 14:27:19,060 : INFO : 1st phase: constructing (12, 102) action matrix\n",
      "2017-05-22 14:27:19,064 : INFO : orthonormalizing (12, 102) action matrix\n",
      "2017-05-22 14:27:19,068 : INFO : 2nd phase: running dense svd on (12, 9) matrix\n",
      "2017-05-22 14:27:19,070 : INFO : computing the final decomposition\n",
      "2017-05-22 14:27:19,073 : INFO : keeping 2 factors (discarding 43.156% of energy spectrum)\n",
      "2017-05-22 14:27:19,076 : INFO : processed documents up to #9\n",
      "2017-05-22 14:27:19,078 : INFO : topic #0(3.341): 0.644*\"system\" + 0.404*\"user\" + 0.301*\"eps\" + 0.265*\"response\" + 0.265*\"time\" + 0.240*\"computer\" + 0.221*\"human\" + 0.206*\"survey\" + 0.198*\"interface\" + 0.036*\"graph\"\n",
      "2017-05-22 14:27:19,081 : INFO : topic #1(2.542): 0.623*\"graph\" + 0.490*\"trees\" + 0.451*\"minors\" + 0.274*\"survey\" + -0.167*\"system\" + -0.141*\"eps\" + -0.113*\"human\" + 0.107*\"response\" + 0.107*\"time\" + -0.072*\"interface\"\n"
     ]
    }
   ],
   "source": [
    "lsi = models.LsiModel(corpus, id2word=dictionary, num_topics=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now suppose a user typed in the query *“Human computer interaction”*. We would like to sort our nine corpus documents in decreasing order of relevance to this query. Unlike modern search engines, here we only concentrate on a single aspect of possible similarities—on apparent semantic relatedness of their texts (words). No hyperlinks, no random-walk static ranks, just a semantic extension over the boolean keyword match:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(0, 0.46182100453271535), (1, -0.070027665279000437)]\n"
     ]
    }
   ],
   "source": [
    "doc = \"Human computer interaction\"\n",
    "vec_bow = dictionary.doc2bow(doc.lower().split())\n",
    "vec_lsi = lsi[vec_bow] # convert the query to LSI space\n",
    "print(vec_lsi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In addition, we will be considering [cosine](http://en.wikipedia.org/wiki/Cosine_similarity) similarity to determine the similarity of two vectors. Cosine similarity is a standard measure in Vector Space Modeling, but wherever the vectors represent probability distributions, [different similarity measures](http://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence#Symmetrised_divergence) may be more appropriate."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Initializing query structures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To prepare for similarity queries, we need to enter all documents which we want to compare against subsequent queries. In our case, they are the same nine documents used for training LSI, converted to 2-D LSA space. But that’s only incidental, we might also be indexing a different corpus altogether."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2017-05-22 14:27:19,299 : WARNING : scanning corpus to determine the number of features (consider setting `num_features` explicitly)\n",
      "2017-05-22 14:27:19,358 : INFO : creating matrix with 9 documents and 2 features\n"
     ]
    }
   ],
   "source": [
    "index = similarities.MatrixSimilarity(lsi[corpus]) # transform corpus to LSI space and index it"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> <B>Warning</B>:\n",
    "> The class `similarities.MatrixSimilarity` is only appropriate when the whole set of vectors fits into memory. For example, a corpus of one million documents would require 2GB of RAM in a 256-dimensional LSI space, when used with this class.\n",
    "> Without 2GB of free RAM, you would need to use the `similarities.Similarity` class. This class operates in fixed memory, by splitting the index across multiple files on disk, called shards. It uses `similarities.MatrixSimilarity` and `similarities.SparseMatrixSimilarity` internally, so it is still fast, although slightly more complex.\n",
    "\n",
    "Index persistency is handled via the standard save() and load() functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2017-05-22 14:27:52,760 : INFO : saving MatrixSimilarity object under C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.index, separately None\n",
      "2017-05-22 14:27:52,772 : INFO : saved C:\\Users\\chaor\\AppData\\Local\\Temp\\deerwester.index\n"
     ]
    }
   ],
   "source": [
    "index.save(os.path.join(TEMP_FOLDER, 'deerwester.index'))\n",
    "#index = similarities.MatrixSimilarity.load(os.path.join(TEMP_FOLDER, 'index'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is true for all similarity indexing classes (`similarities.Similarity`, `similarities.MatrixSimilarity` and `similarities.SparseMatrixSimilarity`). Also in the following, index can be an object of any of these. When in doubt, use `similarities.Similarity`, as it is the most scalable version, and it also supports adding more documents to the index later."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Performing queries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To obtain similarities of our query document against the nine indexed documents:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(0, 0.99809301), (1, 0.93748635), (2, 0.99844527), (3, 0.9865886), (4, 0.90755945), (5, -0.12416792), (6, -0.10639259), (7, -0.098794639), (8, 0.050041765)]\n"
     ]
    }
   ],
   "source": [
    "sims = index[vec_lsi] # perform a similarity query against the corpus\n",
    "print(list(enumerate(sims))) # print (document_number, document_similarity) 2-tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cosine measure returns similarities in the range *<-1, 1>* (the greater, the more similar), so that the first document has a score of 0.99809301 etc.\n",
    "\n",
    "With some standard Python magic we sort these similarities into descending order, and obtain the final answer to the query *“Human computer interaction”*:\n",
    "\n",
    "```\n",
    "sims = sorted(enumerate(sims), key=lambda item: -item[1])\n",
    "print(sims) # print sorted (document number, similarity score) 2-tuples\n",
    "\n",
    "[(2, 0.99844527), # The EPS user interface management system\n",
    "(0, 0.99809301), # Human machine interface for lab abc computer applications\n",
    "(3, 0.9865886), # System and human system engineering testing of EPS\n",
    "(1, 0.93748635), # A survey of user opinion of computer system response time\n",
    "(4, 0.90755945), # Relation of user perceived response time to error measurement\n",
    "(8, 0.050041795), # Graph minors A survey\n",
    "(7, -0.098794639), # Graph minors IV Widths of trees and well quasi ordering\n",
    "(6, -0.1063926), # The intersection graph of paths in trees\n",
    "(5, -0.12416792)] # The generation of random binary unordered trees\n",
    "```\n",
    "\n",
    "(I added the original documents in their “string form” to the output comments, to improve clarity.)\n",
    "\n",
    "The thing to note here is that documents no. 2 (\"`The EPS user interface management system`\") and 4 (\"`Relation of user perceived response time to error measurement`\") would never be returned by a standard boolean fulltext search, because they do not share any common words with \"`Human computer interaction`\". However, after applying LSI, we can observe that both of them received quite high similarity scores (no. 2 is actually the most similar!), which corresponds better to our intuition of them sharing a “computer-human” related topic with the query. In fact, this semantic generalization is the reason why we apply transformations and do topic modelling in the first place.\n",
    "\n",
    "## Where next?\n",
    "\n",
    "Congratulations, you have finished the tutorials – now you know how gensim works :-) To delve into more details, you can browse through the [API documentation](https://radimrehurek.com/gensim/apiref.html), see the [Wikipedia experiments](https://radimrehurek.com/gensim/wiki.html) or perhaps check out [distributed computing](https://radimrehurek.com/gensim/distributed.html) in gensim.\n",
    "\n",
    "Gensim is a fairly mature package that has been used successfully by many individuals and companies, both for rapid prototyping and in production. That doesn’t mean it’s perfect though:\n",
    "\n",
    "* there are parts that could be implemented more efficiently (in C, for example), or make better use of parallelism (multiple machines cores)\n",
    "* new algorithms are published all the time; help gensim keep up by [discussing them](http://groups.google.com/group/gensim) and [contributing code](https://github.com/piskvorky/gensim/wiki/Developer-page)\n",
    "* your **feedback is most welcome** and appreciated (and it’s not just the code!): [idea contributions](https://github.com/piskvorky/gensim/wiki/Ideas-&-Features-proposals), [bug reports](https://github.com/piskvorky/gensim/issues) or just consider contributing [user stories and general questions](http://groups.google.com/group/gensim/topics).\n",
    "Gensim has no ambition to become an all-encompassing framework, across all NLP (or even Machine Learning) subfields. Its mission is to help NLP practicioners try out popular topic modelling algorithms on large datasets easily, and to facilitate prototyping of new algorithms for researchers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
